package com.life.model;

import java.awt.Dimension;
import java.util.ArrayList;

import com.life.constants.Constants;
import com.life.model.event.ModelBoardResetEvent;
import com.life.model.event.ModelBoardRuleSetEvent;
import com.life.model.event.ModelBoardUpdateEvent;
import com.life.rule.ILifeRule;
import com.math.ArrayMath;

public class LifeModel
{

    protected ArrayList<IModelEventsListener> listeners = new ArrayList<IModelEventsListener>();

    protected int xBounds = Constants.DEFAULT_X_BOUNDS;

    protected int yBounds = Constants.DEFAULT_Y_BOUNDS;

    protected boolean[][] board = null;

    protected ILifeRule currentRule = null;

    public int generation = 0;

    public LifeModel()
    {
	initializeModel();
    }

    /**
         * initializes the lifeModel with the designated sizes
         * 
         * @param x
         * @param y
         */
    public LifeModel(int x, int y)
    {
	xBounds = x;
	yBounds = y;
	initializeModel();
    }

    public boolean[][] getRawBoardCopy()
    {
	return ArrayMath.copy(board);
    }
    
  
    
    private LifeModel(int x, int y, boolean[][] board,
	    ArrayList<IModelEventsListener> listeners, ILifeRule rule)
    {
	xBounds = x;
	yBounds = y;

	this.board = new boolean[board.length][board[0].length];
	/* initialize board to all false */
	for (int i = 0; i < xBounds; i++)
	{
	    for (int j = 0; j < yBounds; j++)
	    {
		this.board[i][j] = board[i][j];
	    }
	}

	this.currentRule = rule;
	this.listeners = listeners;
    }

    /**
         * Initializes the internal model components
         */
    protected void initializeModel()
    {
	/* initialize the model to the correct size */
	board = new boolean[xBounds + 2][yBounds + 2];
	
	/* initialize board to all false */
	reset();

	/* initialize listener space */
	listeners = new ArrayList<IModelEventsListener>(
		Constants.SMALL_COLLECTION_SIZE);

    }

    public void addModelEventsListener(IModelEventsListener listener)
    {
	listeners.add(listener);
    }

    public LifeModel deepCloneModel()
    {
	return new LifeModel(xBounds, yBounds, board, listeners, currentRule);
    }

    public Dimension getSize()
    {
	return new Dimension(xBounds, yBounds);
    }

    public boolean isAlive(int x, int y)
    {
	return getValue(x,y);
    }

    public void setAlive(int x, int y)
    {
	setValue(x, y, Constants.ALIVE);
    }

    public void setDead(int x, int y)
    {
	setValue(x, y, Constants.DEAD);
    }

    public boolean getValue(int x, int y)
    {
	if(isValid(x,y))
	{
	    return board[x + 1][y + 1];
	}
	return false;
    }

    public void setValue(int x, int y, boolean val)
    {
	board[Math.max(0, (x + 1) % xBounds)][Math.max(0, (y + 1) % yBounds)] = val;
    }

    public void reset()
    {
	for (int i = 0; i < xBounds; i++)
	{
	    for (int j = 0; j < yBounds; j++)
	    {
		board[i][j] = Constants.DEAD;
	    }
	}
	generation = 0;

	fireModelBoardResetEvent();
    }

    public int getGenerationCount()
    {
	return generation;
    }

    public int getNeighborCount(int x, int y)
    {
	int boardX = x + 1;
	int boardY = y + 1;

	int returnVal = 0;
	
	/* cleaned up this code a little bit */
	returnVal = returnVal + (getValue(x-1,y-1)?1:0);
	returnVal = returnVal + (getValue(x-1,y)?1:0);
	returnVal = returnVal + (getValue(x-1,y+1)?1:0);
	returnVal = returnVal + (getValue(x,y-1)?1:0);
	returnVal = returnVal + (getValue(x,y+1)?1:0);
	returnVal = returnVal + (getValue(x+1,y-1)?1:0);
	returnVal = returnVal + (getValue(x+1,y)?1:0);
	returnVal = returnVal + (getValue(x+1,y+1)?1:0);

	return returnVal;
    }

    @Override
    public String toString()
    {
	String returnVal = "";

	for (int i = 0; i < xBounds; i++)
	{
	    returnVal = returnVal + "|";
	    for (int j = 0; j < yBounds; j++)
	    {
		if (isAlive(i, j))
		{
		    returnVal = returnVal + "X";
		} else
		{
		    returnVal = returnVal + "_";
		}
	    }
	    returnVal = returnVal + "|\n";
	}

	return returnVal;
    }

    public void fireOnUpdateEvent()
    {
	for (IModelEventsListener listener : listeners)
	{
	    listener.modelBoardUpdated(new ModelBoardUpdateEvent(this));
	}
    }

    public void runRule()
    {
	LifeModel newModel = currentRule.runRule(this);
	replaceBoard(newModel);
	generation++;
	this.fireOnUpdateEvent();
    }

    public void replaceBoard(LifeModel newModel)
    {
	for (int i = 0; i < xBounds; i++)
	{
	    for (int j = 0; j < yBounds; j++)
	    {
		setValue(i, j, newModel.isAlive(i, j));
	    }
	}
	this.fireOnUpdateEvent();
    }
    
    public void replaceRawBoard(boolean value[][], int gen)
    {
	for (int i = 0; i < xBounds; i++)
	{
	    for (int j = 0; j < yBounds; j++)
	    {
		if(value.length > i+1 && value[0].length > j+1)
		{
		 setValue(i, j, value[i + 1][j + 1]);
		}
	    }
	}
	generation = gen;
	this.fireOnUpdateEvent();
    }
    
    public int getLifeCount()
    {
	int result = 0;
	for (int i = 0; i < xBounds; i++)
	{
	    for (int j = 0; j < yBounds; j++)
	    {
		result+=(isAlive(i,j))?1:0;
	    }
	}
	return result;
    }

    public void adjustBoardSize(int width, int height)
    {
	boolean[][] oldBoard = board;
	int oldX = xBounds;
	int oldY = yBounds;

	int adjustX = 0;
	int adjustY = 0;

	adjustX = (width < xBounds) ? width : xBounds;
	adjustY = (height < yBounds) ? height : yBounds;

	System.out.println("adjustX:" + adjustX + "| adjustY:" + adjustY);

	xBounds = width;
	yBounds = height;

	board = new boolean[width + 2][height + 2];
	reset();

	for (int i = 0; i < adjustX; i++)
	{
	    for (int j = 0; j < adjustY; j++)
	    {
		setValue(i, j, oldBoard[i][j]);
	    }
	}

	fireOnUpdateEvent();
    }

    public ILifeRule getCurrentRule()
    {
	return currentRule;
    }

    public void fireModelBoardResetEvent()
    {
	ModelBoardResetEvent firedEvent = new ModelBoardResetEvent(this);

	for (IModelEventsListener listener : listeners)
	{
	    listener.modelBoardReset(firedEvent);
	}
    }

    public void fireRuleSetEvent()
    {
	ModelBoardRuleSetEvent firedEvent = new ModelBoardRuleSetEvent(this);
	firedEvent.setRule(currentRule);

	for (IModelEventsListener listener : listeners)
	{
	    listener.modelRuleSet(firedEvent);
	}
    }

    public void setCurrentRule(ILifeRule currentRule)
    {
	this.currentRule = currentRule;
	fireRuleSetEvent();
    }
    /**
     * Checks if the specified cell is a valid coordinate on the board.
     * @param x
     * @param y
     * @return
     */
    	public boolean isValid(int x, int y)
    	{
    	    //Normalize coordinates by adding 1.
    	    x+=1;
    	    y+=1;
    	    if(x>0 && y>0) 
            {
    			if(x<board.length && y<board[x].length) {
    			    return true;
                }
            }
    	    //not a valid game board position
            return false;

    	}
}
